﻿using System;
using System.Collections.Generic;
using System.IO;
using vm.Aspects.Properties;

namespace vm.Aspects.Diagnostics
{
    sealed partial class ObjectTextDumper
    {
        #region Basic types handling
        delegate bool dumpBasicValue(object value, TextWriter writer);

        #region Default basic types' dump methods
        static IDictionary<TypeCode, dumpBasicValue> dumpBasicValues = new Dictionary<TypeCode, dumpBasicValue>()
        {
            {TypeCode.DBNull,   (v, w) => { w.Write("DBNull"); return true; } },
            {TypeCode.Boolean,  (v, w) => { w.Write((bool)v); return true; } },
            {TypeCode.Byte,     (v, w) => { w.Write((byte)v); return true; } },
            {TypeCode.SByte,    (v, w) => { w.Write((sbyte)v); return true; } },
            {TypeCode.Char,     (v, w) => { w.Write((char)v); return true; } },
            {TypeCode.Int16,    (v, w) => { w.Write((Int16)v); return true; } },
            {TypeCode.Int32,    (v, w) => { w.Write((Int32)v); return true; } },
            {TypeCode.Int64,    (v, w) => { w.Write((Int64)v); return true; } },
            {TypeCode.UInt16,   (v, w) => { w.Write((UInt16)v); return true; } },
            {TypeCode.UInt32,   (v, w) => { w.Write((UInt32)v); return true; } },
            {TypeCode.UInt64,   (v, w) => { w.Write((UInt64)v); return true; } },
            {TypeCode.Single,   (v, w) => { w.Write((float)v); return true; } },
            {TypeCode.Double,   (v, w) => { w.Write((double)v); return true; } },
            {TypeCode.Decimal,  (v, w) => { w.Write((decimal)v); return true; } },
            {TypeCode.DateTime, (v, w) => { w.Write("{0:o}", v); return true; } },
        };
        #endregion

        /// <summary>
        /// Dumps the value of basic types (all primitive types plus <see cref="String"/>, <see cref="DateTime"/>, <see cref="DateTimeOffset"/>, 
        /// <see cref="TimeSpan"/>, <see cref="Decimal"/> enum-s, <see cref="Guid"/>, Uri).
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="propertyDumpAttribute">The dump attribute associated with the value (e.g. property attribute).</param>
        /// <returns>
        /// <see langword="true"/> if the value was dumped; otherwise <see langword="false"/> (e.g. the value is struct)
        /// </returns>
        bool DumpedBasicValue(
            object value,
            DumpAttribute propertyDumpAttribute)
        {
            if (!(value==null || value.GetType().IsBasicType()))
                return false;

            // if it is a string value, store the max dump length here
            int dumpMaxLength = 0;

            if (value!=null && propertyDumpAttribute != null)
            {
                // should we mask it - dump the mask and return
                if (propertyDumpAttribute.Mask)
                {
                    _writer.Write(propertyDumpAttribute.MaskValue);
                    return true;
                }

                // special formatting - dump and return
                if (propertyDumpAttribute.ValueFormat != Resources.ValueFormat)
                {
                    if (propertyDumpAttribute.ValueFormat == "ToString()")
                        _writer.Write(value.ToString());
                    else
                        _writer.Write(propertyDumpAttribute.ValueFormat, value);
                    return true;
                }

                // get the max dump length
                dumpMaxLength = propertyDumpAttribute.MaxLength;
            }

            // dump the nulls and return
            if (DumpedNullValue(value))
                return true;

            var type = value.GetType();
            var typeCode = Type.GetTypeCode(type);

            // dump enum-s before predefined types b/c they have the type code of Int32
            if (type.IsEnum)
            {
                _writer.Write(
                    Resources.EnumDumpFormat,
                    type.Name,
                    type.Namespace,
                    type.AssemblyQualifiedName,
                    value.ToString());
                return true;
            }

            // dump the predefined types using the dump delegates table (w/o enum-s - have the type code of Int32)
            if (dumpBasicValues.ContainsKey(typeCode))
                return dumpBasicValues[typeCode](value, _writer);
            if (value is UIntPtr  ||  value is IntPtr)
            {
                _writer.Write("0x{0:x16}", value);
                return true;
            }

            // if dumping string - apply the max length attribute
            var stringValue = value as string;

            if (stringValue != null)
            {
                if (dumpMaxLength > 0 &&
                    dumpMaxLength < stringValue.Length)
                    stringValue = stringValue.Substring(0, dumpMaxLength) + "...";

                _writer.Write(stringValue);
                return true;
            }

            if (value is DateTimeOffset)
            {
                _writer.Write("{0:o}", value);
                return true;
            }

            // if not predefined TypeCode - just dump the default
            if (value is Guid     || 
                value is Uri      ||
                value is TimeSpan)
            {
                _writer.Write(value.ToString());
                return true;
            }

            // we should never be here but just in case...
            _writer.Write("[{0} value]", type.Name);
            return false;
        }

        /// <summary>
        /// If the parameter <paramref name="value"/> is <see langword="null"/> outputs the text &lt;null&gt; to the writer.
        /// </summary>
        /// <param name="value">
        /// The value to dump if it is <see langword="null"/>.
        /// </param>
        /// <returns>
        /// <see langword="true"/>If the value was really <see langword="null"/> and was output the the writer.
        /// </returns>
        bool DumpedNullValue(object value)
        {
            if (value != null)
                return false;

            _writer.Write(DumpUtilities.Null);
            return true;
        }
        #endregion
    }
}
